SPDX-FileCopyrightText: 2025 Maëlle Durin & Margot Schuster SPDX-FileCopyrightText: 2025 AlICe laboratory https://alicelab.be
SPDX-License-Identifier: GPL-3.0-or-later
import bpy
import randomGRILLE Nettoyer la scène
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False, confirm=False)
bpy.ops.outliner.orphans_purge()Mappe la valeur ‘value’ de l’intervalle [min_val, max_val] vers l’intervalle [target_min, target_max].
def mapper(value, min_val, max_val, target_min, target_max):    return target_min + ((value - min_val) / (max_val - min_val)) * (
        target_max - target_min
    )Fonction pour créer un trait rectangulaire
def creer_trait(x, y, largeur_x, largeur_y, hauteur=10):Crée un trait rectangulaire positionné à (x, y) avec la largeur et la hauteur spécifiées.
    bpy.ops.mesh.primitive_cube_add(
        size=1, location=(x, y, hauteur / 2)
    )  # Centré à mi-hauteur
    obj = bpy.context.object
    obj.scale = (
        largeur_x / 1,
        largeur_y / 1,
        hauteur / 1,
    )  # Divisé par 2 car la taille de base du cube est 2x2x2Créer 17 traits le long de l’axe X mappés dans un carré de 10x10
x_position = 1  # Position de départ
espacements_x = [
    random.choice([0.5, 1, 2]) for _ in range(16)
]  # 16 espaces pour 17 traits
total_length_x = sum(espacements_x)  # Longueur totale nécessaire
position_x = 1  # Position initiale du premier trait
for i in range(17):  # 17 traits
    x_mapped = mapper(
        position_x, 0, total_length_x, 0, 9
    )  # Mapper la position dans la plage [0, 10]
    creer_trait(
        x_mapped, 5, largeur_x=0.15, largeur_y=10, hauteur=10
    )  # Largeur X = 0.1, Largeur Y = 10, Hauteur = 10
    if i < len(espacements_x):  # Éviter un index hors limi0tes
        position_x += espacements_x[i]  # Avancer la position de l'espacement aléatoireCréer 9 traits le long de l’axe Y mappés dans un carré de 10x10
y_position = 1  # Position de départ
espacements_y = [
    random.choice([0.5, 1, 2]) for _ in range(14)
]  # 8 espaces de 1 pour 9 traits
total_length_y = sum(espacements_y)  # Longueur totale nécessaire
position_y = 1  # Position initiale du premier trait
for i in range(14):  # 9 traits
    y_mapped = mapper(
        position_y, 0, total_length_y, 0, 9.5
    )  # Mapper la position dans la plage [0, 10]
    creer_trait(
        5, y_mapped, largeur_x=10, largeur_y=0.15, hauteur=10
    )  # Largeur X = 10, Largeur Y = 0.1, Hauteur = 10
    if i < len(espacements_y):  # Éviter un index hors limites
        position_y += espacements_y[i]  # Avancer la position de l'espacementMappers pour X et Z
def mapper_X(value):
    mapping = {
        1: -0.4,
        2: 0.6,
        3: 1.2,
        4: 1.8,
        5: 2.3,
        6: 2.8,
        7: 3.4,
        8: 4,
        9: 4.6,
        10: 5,
        11: 5.625,
        12: 6.25,
        13: 6.875,
        14: 7.5,
        15: 8.125,
        16: 8.75,
        17: 9.375,
        18: 10.2,
    }
    return mapping.get(value, value)def mapper_X1(value):
    mapping = {
        1: -0.4,
        2: 0.7,
        3: 1.4,
        4: 2.1,
        5: 2.8,
        6: 3.6,
        7: 5,
        8: 5.73,
        9: 6.4222,
        10: 7.12222,
        11: 7.8999,
        12: 8.5999,
        13: 9.29,
        14: 10.2,
    }
    return mapping.get(value, value)def mapper_Z(value):
    mapping = {1: 0, 2: 1.43, 3: 2.8, 4: 4.3, 5: 5.1, 6: 6.5, 7: 7.2, 8: 8.5}
    return mapping.get(value, value)def interpolate_points(points, target_count):
    interpolated = []
    step = (len(points) - 1) / (target_count - 1)
    for i in range(target_count):
        idx = i * step
        lower = int(idx)
        upper = min(lower + 1, len(points) - 1)
        t = idx - lowerInterpolation linéaire
        x = (1 - t) * points[lower][0] + t * points[upper][0]
        y = (1 - t) * points[lower][1] + t * points[upper][1]
        z = (1 - t) * points[lower][2] + t * points[upper][2]
        interpolated.append((x, y, z))
    return interpolateddef create_bezier_curve(name, points):
    curve_data = bpy.data.curves.new(name=name, type="CURVE")
    curve_data.dimensions = "3D"
    spline = curve_data.splines.new(type="BEZIER")
    spline.bezier_points.add(len(points) - 1)
    for i, (x, y, z) in enumerate(points):
        bezier_point = spline.bezier_points[i]
        bezier_point.co = (x, y, z)
        bezier_point.handle_left_type = "AUTO"
        bezier_point.handle_right_type = "AUTO"
    curve_obj = bpy.data.objects.new(name, curve_data)
    bpy.context.collection.objects.link(curve_obj)
    return curve_objdef convert_curve_to_mesh(curve_obj):
    bpy.context.view_layer.objects.active = curve_obj
    curve_obj.select_set(True)
    bpy.ops.object.convert(target="MESH")
    return curve_objpoints_curve1 = [
    (mapper_X(i + 1), -0.3, mapper_Z(random.choice([3, 4, 6]))) for i in range(18)
]points_curve2 = [
    (mapper_X1(i + 1), 10.4, mapper_Z(random.choice([2, 3, 4, 6]))) for i in range(14)
]n_points1 = len(points_curve1)
n_points2 = len(points_curve2)
if n_points1 > n_points2:
    points_curve2 = interpolate_points(points_curve2, n_points1)
else:
    points_curve1 = interpolate_points(points_curve1, n_points2)curve1 = create_bezier_curve("BezierCurve1", points_curve1)
curve2 = create_bezier_curve("BezierCurve2", points_curve2)convert_curve_to_mesh(curve1)
convert_curve_to_mesh(curve2)verts1 = [v.co for v in curve1.data.vertices]
verts2 = [v.co for v in curve2.data.vertices]plane_mesh = bpy.data.meshes.new("ConnectingSurface")
plane_object = bpy.data.objects.new("ConnectingSurface", plane_mesh)
bpy.context.collection.objects.link(plane_object)vertices = verts1 + verts2
faces = []Relier les sommets des deux courbes par des quads
n = len(verts1)  # Les deux courbes ont maintenant le même nombre de points
for i in range(n - 1):
    faces.append([i, i + 1, n + i + 1, n + i])plane_mesh.from_pydata(vertices, [], faces)
plane_mesh.update()
bpy.ops.object.select_all(action="DESELECT")
bpy.data.objects["ConnectingSurface"].select_set(True)
bpy.ops.transform.translate(
    value=(-0, -0, -1.2),
    orient_type="LOCAL",
    orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
    orient_matrix_type="LOCAL",
    constraint_axis=(False, False, True),
    mirror=False,
    use_proportional_edit=False,
    proportional_edit_falloff="SMOOTH",
    proportional_size=1,
    use_proportional_connected=False,
    use_proportional_projected=False,
    snap=False,
    snap_elements={"INCREMENT"},
    use_snap_project=False,
    snap_target="CLOSEST",
    use_snap_self=True,
    use_snap_edit=True,
    use_snap_nonedit=True,
    use_snap_selectable=False,
)code pour faire de la courbe un volume solide et action booléenne par la suite
bpy.ops.object.select_all(action="DESELECT")
bpy.data.objects["ConnectingSurface"].select_set(True)
bpy.context.view_layer.objects.active = bpy.data.objects["ConnectingSurface"]
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.quads_convert_to_tris(quad_method="BEAUTY", ngon_method="BEAUTY")
bpy.ops.object.editmode_toggle()
bpy.ops.object.duplicate_move(
    OBJECT_OT_duplicate={"linked": False, "mode": "TRANSLATION"},
    TRANSFORM_OT_translate={"value": (0, 0, 0)},
)
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.extrude_region_move(
    MESH_OT_extrude_region={
        "use_normal_flip": False,
        "use_dissolve_ortho_edges": False,
        "mirror": False,
    },
    TRANSFORM_OT_translate={
        "value": (0, 0, 3.8),
        "orient_type": "LOCAL",
        "orient_matrix": ((1, 0, 0), (0, 1, 0), (0, 0, 1)),
        "orient_matrix_type": "LOCAL",
        "constraint_axis": (False, False, True),
        "mirror": False,
        "use_proportional_edit": False,
        "proportional_edit_falloff": "SMOOTH",
        "proportional_size": 1,
        "use_proportional_connected": False,
        "use_proportional_projected": False,
        "snap": False,
        "snap_elements": {"INCREMENT"},
        "use_snap_project": False,
        "snap_target": "CLOSEST",
        "use_snap_self": True,
        "use_snap_edit": True,
        "use_snap_nonedit": True,
        "use_snap_selectable": False,
        "snap_point": (0, 0, 0),
        "snap_align": False,
        "snap_normal": (0, 0, 0),
        "gpencil_strokes": False,
        "cursor_transform": False,
        "texture_space": False,
        "remove_on_cancel": False,
        "use_duplicated_keyframes": False,
        "view2d_edge_pan": False,
        "release_confirm": False,
        "use_accurate": False,
        "use_automerge_and_split": False,
    },
)
bpy.ops.object.editmode_toggle()
bpy.ops.object.select_all(action="DESELECT")
for obj in bpy.data.objects:
    if "cube" in obj.name:
        obj.select_set(True)
        bpy.context.view_layer.objects.active = obj
for obj in bpy.data.objects:
    if "Cube" in obj.name:
        obj.select_set(True)
        bpy.context.view_layer.objects.active = obj
        bpy.ops.object.modifier_add(type="BOOLEAN")
        bpy.context.object.modifiers["Boolean"].object = bpy.data.objects[
            "ConnectingSurface.001"
        ]
        bpy.context.object.modifiers["Boolean"].solver = "FAST"
        bpy.ops.object.modifier_apply(modifier="Boolean")
        obj.select_set(False)
bpy.data.objects["ConnectingSurface.001"].select_set(True)
bpy.ops.object.delete(use_global=False)
bpy.data.objects["ConnectingSurface"].select_set(True)
bpy.ops.object.delete(use_global=False)
bpy.data.objects["BezierCurve1"].select_set(True)
bpy.ops.object.delete(use_global=False)
bpy.data.objects["BezierCurve2"].select_set(True)
bpy.ops.object.delete(use_global=False)
bpy.ops.object.select_all(action="DESELECT")
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.join()
bpy.context.object.name = "GRILLE"
bpy.ops.object.select_all(action="DESELECT")Colonne Oboie et clarinette
def mapper_X(value):
    mapping = {1: 1.5, 2: 3.16, 3: 4.72, 4: 6.28, 5: 7.73, 6: 9.5}
    return mapping.get(
        value, value
    )  # Retourne la valeur originale si elle n'est pas dans le mappagedef mapper_Y(value):
    mapping = {1: 1.25, 2: 2.5, 3: 3.75, 4: 5, 5: 6.25, 6: 9.5}
    return mapping.get(value, value)def mapper_Z(value):
    mapping = {1: 2.5, 2: 5, 3: 6.5, 4: 10}
    return mapping.get(value, value)Fonction pour créer une colonne rectangulaire avec rotation
def creer_colonne(x, y, z, largeur):Mapper les coordonnées
    x_mapped = mapper_X(x)
    y_mapped = mapper_Y(y)
    z_mapped = mapper_Z(z)Créer un cube à la position mappée
    bpy.ops.mesh.primitive_cube_add(
        size=1, location=(x_mapped, y_mapped, 10 - z_mapped / 2)
    )  # Divisé par 2 pour centrer la base
    obj = bpy.context.object
    obj.scale = (
        largeur / 2.8,
        largeur / 2.8,
        z_mapped / 1,
    )  # Ajuster la largeur et la hauteurFonction pour générer des colonnes
def generer_colonnes(x_range, y_values, z_range, largeur, n_colonnes):
    coordinates = set()
    objets = []
    while len(coordinates) < n_colonnes:
        x = random.choice(x_range)
        y = random.choice(y_values)
        z = random.choice(z_range)
        coordinates.add((x, y, z))
    for coord in coordinates:
        creer_colonne(*coord, largeur)
        objets.append(
            bpy.context.object
        )  # Ajouter chaque colonne à la liste des objets
    return objetsFonction pour joindre les colonnes et leur donner un nom
def joindre_colonnes(objets, nom):
    bpy.ops.object.select_all(action="DESELECT")
    for obj in objets:
        obj.select_set(True)
    bpy.context.view_layer.objects.active = objets[0]
    bpy.ops.object.join()
    bpy.context.object.name = nomParamètres pour chaque ensemble de colonnes
colonnes_configurations = [
    {
        "x_range": [1, 2, 3, 4, 5, 6],
        "y_values": [2, 3, 4, 6],
        "z_range": [1, 2, 4],
        "largeur": 0.5,
        "n_colonnes": 11,
    },
]Générer les colonnes pour chaque configuration et les joindre
for config in colonnes_configurations:
    objets_colonnes = generer_colonnes(
        x_range=config["x_range"],
        y_values=config["y_values"],
        z_range=config["z_range"],
        largeur=config["largeur"],
        n_colonnes=config["n_colonnes"],
    )
    joindre_colonnes(objets_colonnes, "colonneOboie")Colonne CLARINETTE
def mapper_X(value):
    mapping = {1: 1.5, 2: 3.16, 3: 4.72, 4: 6.28, 5: 7.73, 6: 9.5}
    return mapping.get(
        value, value
    )  # Retourne la valeur originale si elle n'est pas dans le mappagedef mapper_Y(value):
    mapping = {1: 1.25, 2: 2.5, 3: 3.75, 4: 5, 5: 6.25, 6: 9.5}
    return mapping.get(value, value)def mapper_Z(value):
    mapping = {1: 2.5, 2: 5, 3: 6.5, 4: 10}
    return mapping.get(value, value)Fonction pour créer une colonne rectangulaire avec rotation
def creer_colonne(x, y, z, largeur):Mapper les coordonnées
    x_mapped = mapper_X(x)
    y_mapped = mapper_Y(y)
    z_mapped = mapper_Z(z)Créer un cube à la position mappée
    bpy.ops.mesh.primitive_cube_add(
        size=1, location=(x_mapped, y_mapped, z_mapped / 2)
    )  # Divisé par 2 pour centrer la base
    obj = bpy.context.object
    obj.scale = (
        largeur / 2.8,
        largeur / 2.8,
        z_mapped / 1,
    )  # Ajuster la largeur et la hauteurFonction pour générer des colonnes
def generer_colonnes(x_range, y_values, z_range, largeur, n_colonnes):
    coordinates = set()
    objets = []
    while len(coordinates) < n_colonnes:
        x = random.choice(x_range)
        y = random.choice(y_values)
        z = random.choice(z_range)
        coordinates.add((x, y, z))
    for coord in coordinates:
        creer_colonne(*coord, largeur)
        objets.append(
            bpy.context.object
        )  # Ajouter chaque colonne à la liste des objets
    return objetsFonction pour joindre les colonnes et leur donner un nom
def joindre_colonnes(objets, nom):
    bpy.ops.object.select_all(action="DESELECT")
    for obj in objets:
        obj.select_set(True)
    bpy.context.view_layer.objects.active = objets[0]
    bpy.ops.object.join()
    bpy.context.object.name = nomParamètres pour chaque ensemble de colonnes
colonnes_configurations = [
    {
        "x_range": [1, 2, 3, 4, 5, 6],
        "y_values": [1, 2, 3, 4, 5, 6],
        "z_range": [1, 2, 3, 4],
        "largeur": 0.5,
        "n_colonnes": 16,
    },
]Générer les colonnes pour chaque configuration et les joindre
for config in colonnes_configurations:
    objets_colonnes = generer_colonnes(
        x_range=config["x_range"],
        y_values=config["y_values"],
        z_range=config["z_range"],
        largeur=config["largeur"],
        n_colonnes=config["n_colonnes"],
    )
    joindre_colonnes(objets_colonnes, "colonneClarinet")'''
#DIVIDER
def make_boolean(base_shape, negative_shape):
#DIVIDER
    base_shape = base_shape.name
    negative_shape = negative_shape.name
#DIVIDER
#DIVIDER
    bpy.ops.object.select_all(action="SELECT")
#DIVIDER
    bpy.data.objects[negative_shape].select_set(False)
#DIVIDER
    bpy.context.view_layer.objects.active = bpy.data.objects[base_shape]
#DIVIDER
    bpy.ops.object.modifier_add(type="BOOLEAN")
    bpy.context.object.modifiers["Boolean"].object = bpy.data.objects[negative_shape]
    bpy.context.object.modifiers["Boolean"].use_self = True
    bpy.ops.object.modifier_apply(modifier="Boolean")
#DIVIDER
    bpy.ops.object.select_all(action="DESELECT")
#DIVIDER
make_boolean(bpy.data.objects["GRILLE"], bpy.data.objects["colonneOboie"])
make_boolean(bpy.data.objects["GRILLE"], bpy.data.objects["colonneClarinet"])
'''
Creates a Boolean operation between two given objects.
Parameters: base_shape (object): The object to be subtracted from (base). negative_shape (object): The object used to subtract from (negative).
Returns: None
Notes: This function utilizes Blender’s Boolean modifier to create a boolean difference between two objects.
Create the cube for boolean
Select All
Deselect Cube
Plane active objects
Boolean action with to differentiate the Cube with the Plane
Select All
Deselect Cube bpy.data.objects[negative_shape].select_set(True) bpy.ops.object.delete(use_global=False)